
package com.shiku.imserver.common.http;


import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.exception.AioDecodeException;
import org.tio.core.exception.LengthOverflowException;
import org.tio.core.utils.ByteBufferUtils;
import org.tio.http.common.HttpConfig;
import org.tio.http.common.RequestLine;
import org.tio.http.common.UploadFile;
import org.tio.http.common.utils.HttpParseUtils;
import org.tio.utils.SystemTimer;
import org.tio.utils.hutool.StrUtil;


public class HttpMultiBodyDecoder {
    public static class Header {
        private String contentDisposition = "form-data";
        private String name = null;
        private String filename = null;
        private String contentType = null;

        private Map<String, String> map = new HashMap<>();


        public String getContentDisposition() {

            return this.contentDisposition;

        }


        public String getContentType() {

            return this.contentType;

        }


        public String getFilename() {

            return this.filename;

        }


        public Map<String, String> getMap() {

            return this.map;

        }


        public String getName() {

            return this.name;

        }


        public void setContentDisposition(String contentDisposition) {

            this.contentDisposition = contentDisposition;

        }


        public void setContentType(String contentType) {

            this.contentType = contentType;

        }


        public void setFilename(String filename) {

            this.filename = filename;

        }


        public void setMap(Map<String, String> map) {

            this.map = map;

        }


        public void setName(String name) {

            this.name = name;

        }

    }


    public static interface MultiBodyHeaderKey {
        public static final String Content_Disposition = "Content-Disposition".toLowerCase();
        public static final String Content_Type = "Content-Type".toLowerCase();

    }


    public enum Step {
        BOUNDARY, HEADER, BODY, END;

    }


    private static Logger log = LoggerFactory.getLogger(HttpMultiBodyDecoder.class);


    public static void decode(HttpRequest request, RequestLine firstLine, byte[] bodyBytes, String initboundary, ChannelContext channelContext, HttpConfig httpConfig) throws AioDecodeException {

        if (StrUtil.isBlank(initboundary)) {

            throw new AioDecodeException("boundary is null");

        }


        long start = SystemTimer.currTime;


        ByteBuffer buffer = ByteBuffer.wrap(bodyBytes);

        buffer.position(0);


        String boundary = "--" + initboundary;

        String endBoundary = boundary + "--";


        Step step = Step.BOUNDARY;


        try {

            while (true) {

                if (step == Step.BOUNDARY) {

                    String line = ByteBufferUtils.readLine(buffer, request.getCharset(), Integer.valueOf(512));


                    if (boundary.equals(line)) {
                        step = Step.HEADER;
                    } else {
                        if (endBoundary.equals(line)) {

                            break;

                        }


                        throw new AioDecodeException("line need:" + boundary + ", but is: " + line + "");
                    }


                }


                Header multiBodyHeader = new Header();

                if (step == Step.HEADER) {

                    List<String> lines = new ArrayList<>(2);

                    while (true) {

                        String line = ByteBufferUtils.readLine(buffer, request.getCharset(), Integer.valueOf(512));

                        if ("".equals(line)) {

                            break;

                        }

                        lines.add(line);

                    }


                    parseHeader(lines, multiBodyHeader, channelContext);

                    step = Step.BODY;

                }


                if (step == Step.BODY) {

                    Step newParseStep = parseBody(multiBodyHeader, request, buffer, boundary, endBoundary, channelContext, httpConfig);

                    step = newParseStep;


                    if (step == Step.END) {

                        break;

                    }

                }


            }

        } catch (LengthOverflowException loe) {

            throw new AioDecodeException(loe);

        } catch (UnsupportedEncodingException e) {

            log.error(channelContext.toString(), e);

        } finally {

            long end = SystemTimer.currTime;

            long iv = end - start;

            log.info("解析耗时:{}ms", Long.valueOf(iv));

        }

    }


    public static Step parseBody(Header header, HttpRequest request, ByteBuffer buffer, String boundary, String endBoundary, ChannelContext channelContext, HttpConfig httpConfig) throws UnsupportedEncodingException, LengthOverflowException, AioDecodeException {

        int initPosition = buffer.position();


        while (buffer.hasRemaining()) {

            String line = ByteBufferUtils.readLine(buffer, request.getCharset(), Integer.valueOf(httpConfig.getMaxLengthOfMultiBody()));

            boolean isEndBoundary = endBoundary.equals(line);

            boolean isBoundary = boundary.equals(line);

            if (isBoundary || isEndBoundary) {

                int startIndex = initPosition;

                int endIndex = buffer.position() - (line.getBytes()).length - 2 - 2;

                int length = endIndex - startIndex;

                byte[] dst = new byte[length];


                System.arraycopy(buffer.array(), startIndex, dst, 0, length);

                String filename = header.getFilename();

                if (filename != null) {


                    if (StrUtil.isNotBlank(filename)) {

                        UploadFile uploadFile = new UploadFile();

                        uploadFile.setName(filename.replaceAll("%", ""));

                        uploadFile.setData(dst);

                        uploadFile.setSize(dst.length);

                        request.addParam(header.getName(), uploadFile);

                    }

                } else {

                    request.addParam(header.getName(), new String(dst, request.getCharset()));

                }

                if (isEndBoundary) {

                    return Step.END;

                }

                return Step.HEADER;

            }

        }


        log.error("文件上传，协议不对，step is null");

        throw new AioDecodeException("step is null");

    }


    public static void parseHeader(List<String> lines, Header header, ChannelContext channelContext) throws AioDecodeException {

        if (lines == null || lines.size() == 0) {

            throw new AioDecodeException("multipart_form_data 格式不对，没有头部信息");

        }


        try {

            for (String line : lines) {

                String[] keyvalue = line.split(":");

                String key = StrUtil.trim(keyvalue[0]).toLowerCase();

                String value = StrUtil.trim(keyvalue[1]);

                header.map.put(key, value);

            }


            String contentDisposition = (String) header.map.get(MultiBodyHeaderKey.Content_Disposition);

            String name = HttpParseUtils.getSubAttribute(contentDisposition, "name");

            String filename = HttpParseUtils.getSubAttribute(contentDisposition, "filename");

            String contentType = (String) header.map.get(MultiBodyHeaderKey.Content_Type);


            header.setContentDisposition(contentDisposition);

            header.setName(name);

            header.setFilename(filename);

            header.setContentType(contentType);

        } catch (Throwable e) {

            log.error(channelContext.toString(), e);

            throw new AioDecodeException(e.toString());

        }

    }

}


